package com.dannypatterson.airinstaller {
	
	import com.dannypatterson.airinstaller.events.UpdateEvent;
	
	import flash.desktop.NativeApplication;
	import flash.desktop.Updater;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.net.SharedObject;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLStream;
	import flash.utils.ByteArray;
	
	/**
	 * This class is used to manage updates within an AIR application.  It can be
	 * automatic or manual and checks for new version by looking at a remote XML
	 * release file.
	 * 
	 * @author Danny Patterson
	 */
	public class UpdateManager extends EventDispatcher {
		
		private var preferences:SharedObject;
		private var releaseCheckLoader:URLLoader;
		private var downloadStream:URLStream;
		private var releaseUrl:String;
		private var releaseData:XML;
		private var autoUpdate:Boolean;
		
		/**
		 * Returns true if the UpdateManager recently checked for a new version and
		 * found one, but the new .air file has not yet been downloaded.  This is not
		 * persistant, so if the application is closed it would need to check for
		 * updates again.
		 */
		public function get isDownloadPending():Boolean {
			var air:Namespace = new Namespace("http://ns.adobe.com/air/application/1.0");
			return releaseData != null && NativeApplication.nativeApplication.applicationDescriptor.air::version.toString() != releaseData.@version.toString();
		}
		
		/**
		 * Returns true if a new version is downloaded, but has not yet been installed.
		 * This is persistant so if the application is closed, it will remember that
		 * there is a new version ready to install. 
		 */
		public function get isUpdatePending():Boolean {
			return preferences.data.pendingUpdateVersion != null;
		}
		
		/**
		 * @param releaseUrl (String) The URL of the release XML file defining the latest releases.
		 * @param autoUpdate (Boolean) [false] If set to true it will check for updates automatically
		 * every 24 hours upon creation. This parameter defaults to false.
		 */
		public function UpdateManager(releaseUrl:String, autoUpdate:Boolean = false) {
			this.releaseUrl = releaseUrl;
			this.autoUpdate = autoUpdate;
			// TODO: Make SharedObject unique per AIR application
			preferences = SharedObject.getLocal("UpdateManager_0.1.8");
			if(!isUpdatePending && autoUpdate) {
				if(preferences.data.lastUpdateCheck != null) {
					var oneDay:Number = 1000 * 60 * 60 * 24;
					var checkAgain:Date = new Date(preferences.data.lastUpdateCheck.valueOf() + oneDay)
					if(checkAgain < new Date()) {
						checkForUpdates();
					}
				}else {
					checkForUpdates();
				}
			}
		}
		
		private function onReleaseComplete(event:Event):void {
			preferences.data.lastUpdateCheck = new Date();
			releaseData = XML(releaseCheckLoader.data).release[0];
			if(isDownloadPending) {
				// TODO: check if update is required
				dispatchEvent(new UpdateEvent(UpdateEvent.UPDATE_AVAILABLE, this));
			}else {
				dispatchEvent(new UpdateEvent(UpdateEvent.UPDATE_UNAVAILABLE, this));
			}
			dispatchEvent(new UpdateEvent(UpdateEvent.CHECK_COMPLETE, this));
		}
		
		private function onDownloadComplete(event:Event):void {
			var fileData:ByteArray = new ByteArray();
			downloadStream.readBytes(fileData, 0, downloadStream.bytesAvailable);
			var updateFileName:String = releaseData.@file.toString().split("/").pop().toString();
			preferences.data.updateFileName = updateFileName;
			var file:File = File.applicationStorageDirectory.resolvePath(updateFileName);
		    var fileStream:FileStream = new FileStream();
		    fileStream.addEventListener(Event.CLOSE, onInstallFileClosed, false, 0, true);
		    fileStream.openAsync(file, FileMode.WRITE);
		    fileStream.writeBytes(fileData, 0, fileData.length);
		    fileStream.close();
		}
		
		private function onInstallFileClosed(event:Event):void {
			preferences.data.pendingUpdateVersion = releaseData.@version.toString();
			dispatchEvent(new UpdateEvent(UpdateEvent.UPDATE_READY, this));
			dispatchEvent(new UpdateEvent(UpdateEvent.DOWNLOAD_COMPLETE, this));
		}
		
		/**
		 * Initiates a request to the release XML file to check for new versions.
		 */
		public function checkForUpdates():void {
			var request:URLRequest = new URLRequest();
			request.url = releaseUrl;
			releaseCheckLoader = new URLLoader();
			releaseCheckLoader.addEventListener(Event.COMPLETE, onReleaseComplete, false, 0, true);
			// TODO: Listen for load errors
			releaseCheckLoader.load(request);
			dispatchEvent(new UpdateEvent(UpdateEvent.CHECK_BEGIN, this));
		}
		
		/**
		 * Initiates the download if there is a download pending.
		 */
		public function downloadUpdate():Boolean {
			if(isDownloadPending) {
				downloadStream = new URLStream();
				downloadStream.addEventListener(Event.COMPLETE, onDownloadComplete, false, 0, true);
				// TODO: Listen for download errors
				downloadStream.load(new URLRequest(releaseData.@file.toString()));
				dispatchEvent(new UpdateEvent(UpdateEvent.DOWNLOAD_BEGIN, this));
				return true;
			}
			return false;
		}
		
		/**
		 * Executes the install if there is an update pending.
		 */
		public function installUpdate():Boolean {
			if(isUpdatePending) {
				var updater:Updater = new Updater();
				var file:File = File.applicationStorageDirectory.resolvePath(preferences.data.updateFileName);
				updater.update(file, preferences.data.pendingUpdateVersion);
				preferences.data.pendingUpdateVersion = null;
				preferences.data.updateFileName = null;
				return true;
			}
			return false;
		}
		
	}
	
}